//@author A0081058H
package net.mysoc.w111j.util;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import net.mysoc.w111j.model.Expense;
import net.mysoc.w111j.model.Money;

/**
 * Useful statistics methods. Primarily used for reporting.
 *
 * Included methods are: sum, mean, variance and standard deviation.
 *
 * Not included are median and mode.
 * Median is a little more :effort: to calculate, since Expenses are sorted by date and then ID.
 * Mode is rather useless in this case, unless we use a range of values.
 */
public class Stats {
    /**
     * Gets all available statistics for a {@code List} of {@code Expenses}. Returns the sum, mean, variance and
     * standardDeviation.
     *
     * @param expenses the {@code List} of expenses to calculate statistics for.
     * @return a {@code Map<String, Money>} with keys as the statistic and the respective value as a {@code Money} object.
     */
    public static Map<String, Money> getStats(List<Expense> expenses) {
        HashMap<String, Money> stats = new HashMap<>();

        // I know there is a lot of recalculation, but I left it that way so the code is cleaner and individual methods
        // can still be called.
        stats.put("sum", sum(expenses));
        stats.put("mean", mean(expenses));
        stats.put("variance", variance(expenses));
        stats.put("standardDeviation", new Money(standardDeviation(expenses)));

        return stats;
    }

    /**
     * Calculates the sum of the prices of a {@code List} of {@code Expenses}.
     *
     * @param expenses the {@code List} of expenses to calculate the sum for.
     * @return the sum of prices.
     */
    public static Money sum(List<Expense> expenses) {
        Money sum = new Money(0);

        for (Expense expense : expenses) {
            sum = sum.add(expense.getPrice());
        }

        return sum;
    }

    /**
     * Calculates the mean of the prices of a {@code List} of {@code Expenses}.
     *
     * @param expenses the {@code List} of expenses to calculate the mean for.
     * @return the mean of prices.
     */
    public static Money mean(List<Expense> expenses) {
        if (expenses.size() < 1) {
            // Not actually right
            return new Money(0);
        } else {
            Money sum = new Money(0);

            for (Expense expense : expenses) {
                sum = sum.add(expense.getPrice());
            }

            Money mean = sum.divide(expenses.size());

            return mean;
        }
    }

    /**
     * Calculates the variance of the prices of a {@code List} of {@code Expenses}.
     *
     * @param expenses the {@code List} of expenses to calculate the variance for.
     * @return the variance of prices.
     */
    public static Money variance(List<Expense> expenses) {
        if (expenses.size() <= 1) {
            return new Money(0);
        } else {
            Money mean = mean(expenses);
            Money temp = new Money(0);
            for (Expense expense : expenses) {
                Money diff = mean.subtract(expense.getPrice());
                temp = temp.add(diff.multiply(diff));
            }

            return temp.divide(expenses.size() - 1);
        }
    }

    /**
     * Calculates the standard deviation of the prices of a {@code List} of {@code Expenses}.
     *
     * @param expenses the {@code List} of expenses to calculate the standard deviation for.
     * @return the standard deviation of prices.
     */
    public static double standardDeviation(List<Expense> expenses) {
        return Math.sqrt(variance(expenses).toBigDecimal().doubleValue());
    }
}
